package com.talentech.xproberlib;

import static com.talentech.xproberlib.Utils.log;
import static com.talentech.xproberlib.Utils.simpleObjectDescribe;

import java.lang.reflect.Method;

import de.robv.android.xposed.XC_MethodHook;

public class Hook
{
	static int maxStackDepth = 0;

	public static XC_MethodHook printName = new XC_MethodHook()
	{
		protected void beforeHookedMethod(MethodHookParam param) throws Throwable
		{
			if (param.thisObject != null)
			{
				log(param.method.getDeclaringClass().getName() + "." + param.method.getName() + "()    in: "
						+ simpleObjectDescribe(param.thisObject));
			}
			else
			{
				log("(static) " + param.method.getDeclaringClass().getName() + "." + param.method.getName() + "()");
			}
		}

		protected void afterHookedMethod(MethodHookParam param) throws Throwable
		{
		}
	};

	public static XC_MethodHook printName_Tabbed = new XC_MethodHook()
	{
		protected void beforeHookedMethod(MethodHookParam param) throws Throwable
		{
		}

		protected void afterHookedMethod(MethodHookParam param) throws Throwable
		{
			StringBuilder space = new StringBuilder();
			int stackDepth = getStackDepth(true);
			for (int i = 1; i <= stackDepth; i++)
			{
				space.append(" ");
			}

			if (param.thisObject != null)
			{
				log(space + param.method.getDeclaringClass().getName() + "." + param.method.getName() + "()    in: "
						+ simpleObjectDescribe(param.thisObject));
			}
			else
			{
				log(space + "(static) " + param.method.getDeclaringClass().getName() + "." + param.method.getName() + "()");
			}

//			 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
//			 log("stackTrace.length: " + stackTrace.length);
//			 maxStackDepth = Math.max(stackTrace.length, maxStackDepth);
//			 log("maxStackDepth: " + maxStackDepth);

			// int overFlowThreshold = 200;
			// if(stackTrace.length > overFlowThreshold)
			// {
			// log("!! unHook " + param.method.getName());
			// XposedBridge.unhookMethod(param.method, printNameHook);
			// }

			// log("-");
		}
	};

	public static XC_MethodHook printDetail = new XC_MethodHook()
	{
		protected void beforeHookedMethod(MethodHookParam param) throws Throwable
		{
		}

		protected void afterHookedMethod(MethodHookParam param) throws Throwable
		{
			if (param.thisObject != null)
			{
				log(param.thisObject.getClass().getName() + "." + param.method.getName() + "()");
			}
			else
			{
				log("Static method: " + param.method.getName() + "()");
			}

			if (!(param.method instanceof Method))
			{
				log(param.method.getName() + "is not a Method!");
				return;
			}

			Method method = (Method) param.method;

			String paramString = "";
			for (int i = 0; i < param.args.length; i++)
			{
				if (paramString.length() > 0)
				{
					paramString += ", ";
				}

				paramString += method.getParameterTypes()[i].getSimpleName() + " ";

				Object arg = param.args[i];
				if (arg != null)
				{
					paramString += arg.toString();
				}
				else
				{
					paramString += "void";
				}
			}
			if (paramString.equals(""))
			{
				paramString = "None";
			}
			log("   Param : " + paramString);

			log("   Return: " + method.getReturnType().getSimpleName() + " " + param.getResult());
			log("-");

			printStackTrace(true);
		}
	};

	public static XC_MethodHook printFullDetail = new XC_MethodHook()
	{
		protected void beforeHookedMethod(MethodHookParam param) throws Throwable
		{
		}

		protected void afterHookedMethod(MethodHookParam param) throws Throwable
		{
			if (param.thisObject != null)
			{
				log(param.method.toString() + "()");
			}
			else
			{
				log("Static method: " + param.method.getName() + "()");
			}

			if (!(param.method instanceof Method))
			{
				log(param.method.getName() + "is not a Method!");
				return;
			}

			Method method = (Method) param.method;

			String paramString = "";
			for (int i = 0; i < param.args.length; i++)
			{
				if (paramString.length() > 0)
				{
					paramString += ", ";
				}

				paramString += method.getParameterTypes()[i].getSimpleName() + " ";

				Object arg = param.args[i];
				if (arg != null)
				{
					paramString += arg.toString();
				}
				else
				{
					paramString += "void";
				}
			}
			if (paramString.equals(""))
			{
				paramString = "None";
			}
			log("   Param : " + paramString);

			log("   Return: " + method.getReturnType().getSimpleName() + " " + param.getResult());
			log("-");

			printStackTrace(false);
		}
	};

	public static void printStackTrace(boolean hideXposed)
	{
		StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
		// List<StackTraceElement> newStackTrace = new LinkedList<StackTraceElement>(); // 留待后用
		boolean shouldSkip = true; // 第一个需要跳过
		for (StackTraceElement element : stackTrace)
		{
			if (!hideXposed)
			{
				// newStackTrace.add(element);
				log("at " + element.getClassName() + "." + element.getMethodName());
				continue;
			}

			if (element.getMethodName().equals("invokeOriginalMethodNative") || element.getMethodName().equals("handleHookedMethod")
					|| element.getMethodName().equals("afterHookedMethod"))
			{
				// 在此之后被遍历到的element（实际发生在此之前）为被Xposed劫持的方法，与之后被调用的原方法重复，需隐藏
				if (element.getMethodName().equals("handleHookedMethod"))
				{
					shouldSkip = true;
				}

				continue;
			}

			if (!shouldSkip)
			{
				// newStackTrace.add(element);
				log("at " + element.getClassName() + "." + element.getMethodName());
			}
			else
			{
				shouldSkip = false;
			}
		}

		log("-");
	}

	public static int getStackDepth(boolean hideXposed)
	{
		int result = 0;
		StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
		if (!hideXposed)
		{
			return stackTrace.length;
		}

		boolean shouldSkip = true; // 第一个需要跳过
		for (StackTraceElement element : stackTrace)
		{
			if (element.getMethodName().equals("invokeOriginalMethodNative") || element.getMethodName().equals("handleHookedMethod")
					|| element.getMethodName().equals("afterHookedMethod"))
			{
				// 在此之后被遍历到的element（实际发生在此之前）为被Xposed劫持的方法，与之后被调用的原方法重复，需隐藏
				if (element.getMethodName().equals("handleHookedMethod"))
				{
					shouldSkip = true;
				}
				continue;
			}

			if (!shouldSkip)
			{
				result++;
			}
			else
			{
				shouldSkip = false;
			}
		}

		return result;
	}

}
